package com.rw.tool.util.io;

import com.rw.tool.util.io.exception.StreamIOException;
import com.rw.tool.util.string.StringFormatter;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

/**
 * 文件读写的方法
 */
public class FileIOUtil {
    /**
     * 获取工作目录
     */
    public static String getWorkPath() {
        return System.getProperty("user.dir");
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(String file) {
        return readFile(Paths.get(file), StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(File file) {
        return readFile(file.toPath(), StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(Path file) {
        return readFile(file, StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(String file, Charset charset) {
        return new String(readAllBytes(file), charset);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(File file, Charset charset) {
        return new String(readAllBytes(file), charset);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(URL url) {
        return new String(readAllBytes(url), StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(URL url, Charset charset) {
        return new String(readAllBytes(url), charset);
    }

    /**
     * 读取文件中所有的字符
     * 在类路径中寻找文件
     */
    public static String readFileFromClassPath(String classPath) {
        return readFileFromClassPath(classPath, StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     * 在类路径中寻找文件
     */
    public static String readFileFromClassPath(String classPath, Charset charset) {
        return readFile(FileIOUtil.class.getClassLoader().getResource(classPath), charset);
    }

    /**
     * 读取文件中所有的字符
     * 在jar包同级目录中寻找文件
     */
    public static String readFileFromJarPath(String jarPath) {
        return readFileFromJarPath(jarPath, StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     * 在jar包同级目录中寻找文件
     */
    public static String readFileFromJarPath(String jarPath, Charset charset) {
        String homePath = getWorkPath();
        File file = new File(homePath, jarPath);
        return readFile(file, charset);
    }

    /**
     * 读取配置文件中所有的字符
     * 优先寻找jar包同级目录文件，如果未找到，则寻找类路径中的文件
     */
    public static String readConfig(String configFile) {
        return readConfig(configFile, StandardCharsets.UTF_8);
    }

    /**
     * 读取配置文件中所有的字符
     * 优先寻找jar包同级目录文件，如果未找到，则寻找类路径中的文件
     */
    public static String readConfig(String configFile, Charset charset) {
        String homePath = getWorkPath();
        File file = new File(homePath, configFile);
        if (file.exists()) {
            return readFile(file, charset);
        }
        return readFileFromClassPath(configFile, charset);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(String file, String msg, boolean append) {
        writeFile(file, msg, StandardCharsets.UTF_8, append);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(String file, String msg) {
        writeFile(file, msg, StandardCharsets.UTF_8);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(File file, String msg, boolean append) {
        writeFile(file, msg, StandardCharsets.UTF_8, append);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(File file, String msg) {
        writeFile(file, msg, StandardCharsets.UTF_8);
    }

    /**
     * 读取文件中所有的字符
     */
    public static String readFile(Path file, Charset charset) {
        return new String(readAllBytes(file), charset);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(String file, String msg, Charset charset, boolean append) {
        writeAllBytes(file, msg.getBytes(charset), append);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(String file, String msg, Charset charset) {
        writeAllBytes(file, msg.getBytes(charset));
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(File file, String msg, Charset charset, boolean append) {
        writeAllBytes(file, msg.getBytes(charset), append);
    }

    /**
     * 向文件中写入内容
     */
    public static void writeFile(File file, String msg, Charset charset) {
        writeAllBytes(file, msg.getBytes(charset));
    }


    /**
     * 读取文件中所有的字节
     */
    public static byte[] readAllBytes(String file) {
        return readAllBytes(Paths.get(file));
    }

    /**
     * 读取文件中所有的字节
     */
    public static byte[] readAllBytes(File file) {
        return readAllBytes(file.toPath());
    }

    /**
     * 读取文件中所有的字节
     */
    public static byte[] readAllBytes(Path file) {
        try {
            return Files.readAllBytes(file);
        } catch (IOException e) {
            throw new StreamIOException(e);
        }
    }

    /**
     * 读取文件中所有的字节
     */
    public static byte[] readAllBytes(URL url) {
        try (InputStream inputStream = url.openStream()) {
            return StreamUtil.readAllBytes(inputStream);
        } catch (IOException e) {
            throw new StreamIOException(e);
        }
    }

    /**
     * 读取文件中所有的字节
     */
    public static byte[] readAllBytesFromClassPath(String classPath) {
        return readAllBytes(FileIOUtil.class.getClassLoader().getResource(classPath));
    }

    /**
     * 向文件中写入全部字节
     */
    public static void writeAllBytes(String file, byte[] bytes) {
        writeAllBytes(new File(file), bytes, false);
    }

    /**
     * 向文件中写入全部字节
     */
    public static void writeAllBytes(String file, byte[] bytes, boolean append) {
        writeAllBytes(new File(file), bytes, append);
    }

    /**
     * 向文件中写入全部字节
     */
    public static void writeAllBytes(File file, byte[] bytes) {
        writeAllBytes(file, bytes, false);
    }

    /**
     * 向文件中写入全部字节
     */
    public static void writeAllBytes(File file, byte[] bytes, boolean append) {
        mkParentDirs(file);
        try (FileOutputStream fos = new FileOutputStream(file, append)) {
            StreamUtil.writeAllBytes(fos, bytes);
        } catch (IOException e) {
            throw new StreamIOException(e);
        }
    }

    /**
     * 读取文件保存的对象
     */
    public static <T> T readObject(String file) {
        return readObject(new File(file));
    }

    /**
     * 读取文件保存的对象
     */
    public static <T> T readObject(File file) {
        try (FileInputStream fis = new FileInputStream(file)) {
            return StreamUtil.readObject(fis);
        } catch (IOException e) {
            throw new StreamIOException(e);
        }
    }

    /**
     * 读取文件保存的对象
     */
    public static void writeObject(File file, Serializable obj) {
        mkParentDirs(file);
        try (FileOutputStream fos = new FileOutputStream(file)) {
            StreamUtil.writeObject(fos, obj);
        } catch (IOException e) {
            throw new StreamIOException(e);
        }
    }

    /**
     * 创建父级文件夹
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void mkParentDirs(File file) {
        file.getParentFile().mkdirs();
    }

    /**
     * 创建文件夹
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void mkDirs(File file) {
        file.mkdirs();
    }

    /**
     * 删除文件
     *
     * @param file       文件
     * @param deleteMode 删除失败处理模式
     */
    public static void deleteFile(File file, DeleteMode deleteMode) {
        if (file == null) {
            // 如果file参数为null，并且删除模式为断言，则抛出空指针异常
            if (deleteMode == DeleteMode.ASSERT) {
                String errorMsg = "File can not be null! file : {}";
                throw new NullPointerException(StringFormatter.format(errorMsg, file));
            } else {
                return;
            }
        }

        boolean success = file.delete();

        // 如果删除文件没有成功，并且删除模式为断言，则抛出异常
        if (!success && deleteMode == DeleteMode.ASSERT) {
            String errorMsg = "File delete failed! file : {}";
            throw new IllegalArgumentException(StringFormatter.format(errorMsg, file));
        }
    }

    /**
     * 删除文件
     *
     * @param file 文件
     */
    public static void deleteFile(File file) {
        deleteFile(file, DeleteMode.ASSERT);
    }

    /**
     * 如果文件存在
     * 删除文件或者递归删除文件夹
     *
     * @param folder     文件夹
     * @param deleteMode 删除失败处理模式
     */
    public static void delete(File folder, DeleteMode deleteMode) {
        // 如果参数为文件，则直接删除文件
        if (folder.isFile()) {
            deleteFile(folder, deleteMode);
            return;
        }

        // 获取所有子文件夹，如果没有子文件，则直接删除
        File[] childrenFiles = folder.listFiles();
        if (childrenFiles == null) {
            deleteFile(folder, deleteMode);
            return;
        }

        // 遍历所有子文件夹，删除他们，然后删除自己
        for (File childFile : childrenFiles) {
            delete(childFile, deleteMode);
        }

        // 删除自己
        deleteFile(folder, deleteMode);
    }

    /**
     * 如果文件存在
     * 删除文件或者递归删除文件夹
     *
     * @param folder 文件夹
     */
    public static void delete(File folder) {
        delete(folder, DeleteMode.ASSERT);
    }

    /**
     * 复制文件到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     * @param copyMode 冲突解决方式
     */
    public static void copyFile(File fromFile, File toFile, CopyMode copyMode) {
        // 如果 fromFile 不存在，则直接报错
        if (!fromFile.exists()) {
            String errorMsg = "fromFile is not exists! fromFile : {}, toFile : {}";
            throw new IllegalArgumentException(StringFormatter.format(errorMsg, fromFile, toFile));
        }

        // 检查fromFile和toFile的类别
        if (fromFile.isDirectory() || toFile.isDirectory()) {
            String errorMsg = "fromFile and toFile must be both files! fromFile : {}, toFile : {}";
            throw new IllegalArgumentException(StringFormatter.format(errorMsg, fromFile, toFile));
        }

        // 如果 toFile 存在，并且CopyMode为断言，则直接抛出异常
        if (toFile.exists() && copyMode == CopyMode.ASSERT) {
            String errorMsg = "toFile already exists! fromFile : {}, toFile : {}";
            throw new IllegalArgumentException(StringFormatter.format(errorMsg, fromFile, toFile));
        }

        // 如果 toFile 存在，并且CopyMode为跳过，则直接返回
        if (toFile.exists() && copyMode == CopyMode.SKIP) {
            return;
        }

        FileIOUtil.mkDirs(toFile);

        try {
            // 进行复制
            Files.copy(fromFile.toPath(), toFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            throw new StreamIOException(e);
        }
    }

    /**
     * 复制文件，或者递归复制文件夹到另一个地方
     *
     * @param fromFolder 起始目录
     * @param toFolder   目标目录
     * @param copyMode   冲突解决方式
     */
    public static void copy(File fromFolder, File toFolder, CopyMode copyMode) {
        // 如果fromFile是一个文件，则直接复制这个文件
        if (fromFolder.isFile()) {
            copyFile(fromFolder, toFolder, copyMode);
            return;
        }

        // 获取所有子文件
        File[] childrenFiles = fromFolder.listFiles();
        if (childrenFiles == null) {
            return;
        }

        // 如果为空文件夹，则直接复制当前文件夹
        if (childrenFiles.length == 0) {
            FileIOUtil.mkDirs(toFolder);
        }

        // 遍历所有的子文件
        for (File childFile : childrenFiles) {
            // 如果是文件，则直接复制
            if (childFile.isFile()) {
                // 获取文件名字，得到目标文件
                String name = childFile.getName();
                File toChildFile = new File(toFolder, name);

                // 复制文件
                copyFile(childFile, toChildFile, copyMode);
            } else if (childFile.isDirectory()) {
                // 获取文件夹名字，得到目标文件夹
                String name = childFile.getName();
                File toChildFolder = new File(toFolder, name);

                // 复制文件夹
                copy(childFile, toChildFolder, copyMode);
            } else {
                // 如果既不是文件又不是文件夹，直接抛出异常
                String errorMsg = "unknown file type! file: {}";
                throw new IllegalArgumentException(StringFormatter.format(errorMsg, childFile));
            }
        }
    }

    /**
     * 复制文件到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     */
    public static void copyFile(File fromFile, File toFile) {
        copyFile(fromFile, toFile, CopyMode.ASSERT);
    }

    /**
     * 复制文件，或者递归复制文件夹到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     */
    public static void copy(File fromFile, File toFile) {
        copy(fromFile, toFile, CopyMode.ASSERT);
    }

    /**
     * 移动文件到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     * @param copyMode 冲突解决方式
     */
    public static void moveFile(File fromFile, File toFile, CopyMode copyMode) {
        copyFile(fromFile, toFile, copyMode);
        deleteFile(fromFile, DeleteMode.ASSERT);
    }

    /**
     * 移动文件到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     */
    public static void moveFile(File fromFile, File toFile) {
        moveFile(fromFile, toFile, CopyMode.ASSERT);
    }

    /**
     * 复制文件，或者递归复制文件夹到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     * @param copyMode 冲突解决方式
     */
    public static void move(File fromFile, File toFile, CopyMode copyMode) {
        copy(fromFile, toFile, copyMode);
        delete(fromFile, DeleteMode.ASSERT);
    }

    /**
     * 复制文件，或者递归复制文件夹到另一个地方
     *
     * @param fromFile 起始目录
     * @param toFile   目标目录
     */
    public static void move(File fromFile, File toFile) {
        move(fromFile, toFile, CopyMode.ASSERT);
    }
}
